﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Sustainalytics.ESGRatings.Primitives
{
    public abstract class AbstractDebuggingContext
    {
        
        public int CompaniesCount { get; set; }

        public int ClustersCount { get; set; }

        public int IndicatorsCount { get; set; }

        public int ClusterGroupsCount { get; set; }

        public int NumberOfRuns { get; set; }

        protected List<TimedStep> ContextSetupTimedSteps { get; set; }

        protected Dictionary<Guid,List<MemorySizing>> MemoryStatistics { get; set; } 

        protected Dictionary<int, List<TimedStep>> TimedStepsDictionary { get; set; }

        protected Dictionary<Guid, string> ContextIdentityNameMap { get; set; }

        protected Dictionary<int,Guid> ContextIdentityRunMapDictionary { get; set; }

        public string Output { get; set; }

        public string MemoryOutpout { get; set; }

        protected Guid CurrentGuid;

        protected bool _wasDumped=false;

        protected readonly string[] Properties =
        {
            "NumberOfRuns",
            "CompaniesCount",
            "IndicatorsCount"
        };

        public virtual void NewRun(IEnumerable<TimedStep> results)
        {
            if (NumberOfRuns == 0 && !ContextIdentityNameMap.Any())
            {
                AddExecutionContext(string.Empty);
            }
            NumberOfRuns += 1;

            if (!TimedStepsDictionary.ContainsKey(NumberOfRuns))
            {
                TimedStepsDictionary.Add(NumberOfRuns, new List<TimedStep>(results));
            }

            if (!ContextIdentityRunMapDictionary.ContainsKey(NumberOfRuns))
            {
                ContextIdentityRunMapDictionary.Add(NumberOfRuns,CurrentGuid);
            }
            // TimedStepsDictionary[NumberOfRuns].AddRange();
        }

        public void InitStatisticContext(int companiesCount, int indicatorsCount, int clustersCount,
            int clusterGroupsCount)
        {
            CompaniesCount = companiesCount;
            ClustersCount = clustersCount;
            IndicatorsCount = indicatorsCount;
            ClusterGroupsCount = clusterGroupsCount;
        }

        public virtual void AddComputationContextSetupStep(TimedStep step)
        {
            if (ContextSetupTimedSteps == null)
            {
                ContextSetupTimedSteps = new List<TimedStep>();
            }
            ContextSetupTimedSteps.Add(step);
        }

      

        public virtual void AddMemorySizingStatistic(IEnumerable<MemorySizing> memorySizing)
        {
            if (!this.MemoryStatistics.ContainsKey(CurrentGuid))
            {
                if (CurrentGuid == Guid.Empty)
                {
                    AddExecutionContext("InitContext");
                }
              
                    this.MemoryStatistics.Add(CurrentGuid,new List<MemorySizing>());
               
            }
            this.MemoryStatistics[CurrentGuid].AddRange(memorySizing);
        }

        public abstract void DumpMemoryStatistics();
        

        public virtual void AddToRun(IEnumerable<TimedStep> results)
        {
            if (!TimedStepsDictionary.ContainsKey(NumberOfRuns))
            {
                TimedStepsDictionary.Add(NumberOfRuns, new List<TimedStep>());
            }
            TimedStepsDictionary[NumberOfRuns].AddRange(results);
        }

        public virtual void AddToRun(TimedStep step)
        {
            if (TimedStepsDictionary.ContainsKey(NumberOfRuns))
            {
                TimedStepsDictionary[NumberOfRuns].Add(step);
            }
        }

        public virtual void ResetStatistics()
        {
            TimedStepsDictionary = new Dictionary<int, List<TimedStep>>();
            ContextIdentityRunMapDictionary=new Dictionary<int,Guid>();
            ContextIdentityNameMap = new Dictionary<Guid, string>();
            ContextSetupTimedSteps = new List<TimedStep>();
            if (_wasDumped || MemoryStatistics==null)
            {
                MemoryStatistics = new Dictionary<Guid, List<MemorySizing>>();
            }
            NumberOfRuns = 0;
        }

        public abstract void Dump();

        public abstract void DumpInitData();

        public abstract void Append();

        public virtual void AddExecutionContext(string executionContextDetails)
        {
            if (ContextIdentityNameMap == null)
                ContextIdentityNameMap = new Dictionary<Guid, string>();
            if (ContextIdentityRunMapDictionary == null)
                ContextIdentityRunMapDictionary = new Dictionary<int, Guid>();
            try
            {
                CurrentGuid = Guid.NewGuid();
                if (!CurrentGuid.Equals(Guid.Empty))
                {
                    ContextIdentityNameMap.Add(CurrentGuid, executionContextDetails);
                }
            }
            catch (Exception)
            {
                CurrentGuid = Guid.Empty;
                throw;
            }
        }
    }

    public enum StatisticsType
    {
        None,
        Csv
    }

    public class StatisticsOptions
    {
        public StatisticsOptions(StatisticsType statisticsType, string outputFile,string memoryOutputFile="")
        {
            StatisticsType = statisticsType;
            OutputFile = outputFile;
            if (!String.IsNullOrEmpty(memoryOutputFile))
            {
                MemoryOutputFile = memoryOutputFile;
            }
            else
            {

                MemoryOutputFile = outputFile;
            }
        }

        public StatisticsOptions()
        {
            StatisticsType = StatisticsType.None;
            OutputFile = string.Empty;
        }

        public StatisticsType StatisticsType { get; set; }

        public string OutputFile { get; set; }
        public string MemoryOutputFile { get; set; }
    }

    public class DefaultDebuggingContext : AbstractDebuggingContext
    {
        public override void DumpMemoryStatistics()
        {
            
        }

        public override void Dump()
        {
        }

        public override void DumpInitData()
        {
            throw new NotImplementedException();
        }

        public override void Append()
        {
        }
    }

    public sealed class DebuggingContextCsv : AbstractDebuggingContext
    {
        public DebuggingContextCsv()
        {
            ResetStatistics();
        }

        private string BuildHeader(string separator = ", ")
        {
            string header = String.Join(separator, Properties);
            var steps = TimedStepsDictionary.Values.SelectMany(x => x);
            var stepNames = steps.Select(x => x.Name).Distinct();
            string stepsHeader = String.Join(separator, stepNames.ToArray());
            return String.Format("{0}{1}{2}{3}{4}", header, separator, "runNumber", separator, stepsHeader);
        }

        private string BuildContent(string ContextName, List<TimedStep> steps, string separator = ", ")
        {
            Type t = GetType();
            PropertyInfo[] fields = t.GetProperties();
            StringBuilder line = new StringBuilder();

            foreach (var propertyName in Properties)
            {
                if (line.Length > 0)
                    line.Append(separator);
                PropertyInfo property = fields.FirstOrDefault(x => x.Name == propertyName);
                if (property != null)
                {
                    var x = property.GetValue(this);

                    if (x != null)
                        line.Append(x);
                }
            }

            string content = String.Join(separator, steps.Select(f => f.Milliseconds).ToArray());

            return string.Format("{0}{1}{2}{3}{4}", line, separator, ContextName, separator, content);
        }

        //private string BuildFooter(string separator = ", ")
        //{
        //    Type t = GetType();
        //    PropertyInfo[] fields = t.GetProperties();
        //    StringBuilder line = new StringBuilder();

        //    foreach (var propertyName in Properties)
        //    {
        //        if (line.Length > 0)
        //            line.Append(separator);
        //        PropertyInfo property = fields.FirstOrDefault(x => x.Name == propertyName);
        //        if (property != null)
        //        {
        //            var x = property.GetValue(this);

        //            if (x != null)
        //                line.Append(x);
        //        }
        //    }
        //    var steps = (from item in TimedStepsDictionary.Values.SelectMany(x => x)
        //                 group item by item.Name
        //                     into g
        //                     select g.Average(x => x.Milliseconds)).ToList();

        //    string content = String.Join(separator, steps.ToArray());

        //    return string.Format("{0}{1}{2}{3}{4}", line, separator, 0, separator, content);
        //}

        //private string HeaderToString(List<TimedStep> steps)
        //{
        //    var separator = ",";
        //    string header = String.Join(separator, steps.Select(f => f.Name).ToArray());
        //    //string content = String.Join(separator, steps.Select(f => f.Milliseconds).ToArray());
        //    return String.Format("{0}{1}{2}", "runNumber", separator, header);
        //}

        public override void DumpMemoryStatistics()
        {
            _wasDumped = true;
            var result = new StringBuilder();
            if (!String.IsNullOrEmpty(MemoryOutpout))
            {
                using (CsvFileWriter writer = new CsvFileWriter(MemoryOutpout))
                {
                    writer.WriteLine(@"sep=,");
                    foreach (var kvp in MemoryStatistics)
                    {
                        string contextName;
                        if (ContextIdentityNameMap.TryGetValue(kvp.Key, out contextName))
                        {
                            foreach (var stat in kvp.Value)
                            {
                                writer.WriteLine(String.Format("{0},{1},{2}", contextName, stat.Property, stat.Bytes));
                            }
                        }
                    }
                }
            }
        }

        public override void Dump()
        {
            if (!String.IsNullOrEmpty(Output))
            {
                using (CsvFileWriter writer = new CsvFileWriter(Output))
                {
                    writer.WriteLine(@"sep=,");
                    var header = BuildHeader();
                    writer.WriteLine(header);

                    Append(writer);
                }
            }
        }

        public override void DumpInitData()
        {
            if (!String.IsNullOrEmpty(Output))
            {
                using (CsvFileWriter writer = new CsvFileWriter("InitInput.csv"))
                {
                    writer.WriteLine(@"sep=,");
                    foreach (var item in ContextSetupTimedSteps)
                    {
                        writer.Write(string.Format("{0},{1}",item.Name,item.Milliseconds));
                    }
                }
            }
        }

        private void Append(CsvFileWriter writer)
        {  Guid guid;
            String contextName;
            foreach (var runKvp in TimedStepsDictionary)
            {
              
                if (ContextIdentityRunMapDictionary.TryGetValue(runKvp.Key, out guid))
                {
                   
                    if (ContextIdentityNameMap.TryGetValue(guid, out contextName))
                    {
                        var row = BuildContent(contextName, runKvp.Value);
                        writer.WriteLine(row);
                    }
                }
            }
            // writer.WriteLine(BuildFooter());
        }

        public override void Append()
        {
            if (!String.IsNullOrEmpty(Output))
            {
                using (CsvFileWriter writer = new CsvFileWriter(Output))
                {
                    Append(writer);
                }
            }
        }
    }

    public static class DebuggingContextFactory
    {
        private static readonly Dictionary<StatisticsType, AbstractDebuggingContext> StatisticsTypeDictionary =
            new Dictionary<StatisticsType, AbstractDebuggingContext>()
            {
                {StatisticsType.None, new DefaultDebuggingContext()},
                {StatisticsType.Csv, new DebuggingContextCsv()}
            };

        public static AbstractDebuggingContext GeDebuggingContext(StatisticsOptions options)
        {
            AbstractDebuggingContext context;
            if (StatisticsTypeDictionary.TryGetValue(options.StatisticsType, out context))
            {
                context.Output = options.OutputFile;
                context.MemoryOutpout = options.MemoryOutputFile;
            }
            else
            {
                context = new DefaultDebuggingContext();
            }
            return context;
        }
    }

    /// <summary>
    /// Class to write data to a CSV file
    /// </summary>
    public class CsvFileWriter : StreamWriter
    {
        public CsvFileWriter(Stream stream)
            : base(stream)
        {
        }

        public CsvFileWriter(string filename)
            : base(filename)
        {
        }
    }
}